# 效度分析
import traceback

import pandas as pd
from factor_analyzer.factor_analyzer import FactorAnalyzer, calculate_kmo, calculate_bartlett_sphericity


def do_analysis(df: pd.DataFrame):
    """
    开始分析
    :param df: 需要分析的dataframe
    :return: 返回分析结果
    """

    def process_nan(value):
        if isinstance(value, float) and value != value:
            return None  # 替换 NaN 值为 None 或其他值
        elif isinstance(value, dict):
            return {k: str(process_nan(v)) for k, v in value.items()}
        elif isinstance(value, list):
            return [str(process_nan(v)) for v in value]
        else:
            return str(value)

    def get_result(obj: pd.DataFrame) -> []:
        output = []
        for index, row in obj.iterrows():
            output.append({'row_key': str(index), 'values': {str(col): process_nan(row[col]) for col in obj.columns}})
        return output

    load_matrix = FactorAnalyzer(rotation=None, n_factors=len(df.T), method='principal')
    load_matrix.fit(df)
    f_contribution_var = load_matrix.get_factor_variance()
    matrices_var = pd.DataFrame()  # 旋转前的载荷矩阵的贡献率
    matrices_var["0"] = f_contribution_var[0]
    matrices_var["1"] = f_contribution_var[1]
    matrices_var["2"] = f_contribution_var[2]

    eigenvalues = 1
    count = 0
    for c in matrices_var["0"]:
        if c >= eigenvalues:
            count += 1
        else:
            s = matrices_var["2"][count - 1]
            # print("\n选择了" + str(count) + "个因子累计贡献率为" + str(s) + "\n")
            break

    df_before_matric = pd.DataFrame()  # 旋转前的载荷矩阵的贡献率
    df_before_matric["3"] = f_contribution_var[0][:count]
    df_before_matric["4"] = f_contribution_var[1][:count]
    df_before_matric["5"] = f_contribution_var[2][:count]

    load_matrix_rotated = FactorAnalyzer(rotation='varimax', n_factors=count, method='principal')
    load_matrix_rotated.fit(df)
    f_contribution_var_rotated = load_matrix_rotated.get_factor_variance()
    matrices_var_rotated = pd.DataFrame()  # 旋转后的载荷矩阵的贡献率
    matrices_var_rotated["6"] = f_contribution_var_rotated[0]
    matrices_var_rotated["7"] = f_contribution_var_rotated[1]
    matrices_var_rotated["8"] = f_contribution_var_rotated[2]
    total_matric_df = matrices_var.join(df_before_matric).join(matrices_var_rotated)
    total_matric = get_result(total_matric_df)
    # print("总方差解释")
    # print(total_matric)

    gyzfc = load_matrix_rotated.get_communalities()  # 公因子方差
    column_gyzfc = pd.DataFrame(load_matrix_rotated.get_communalities(), index=df.columns)  # 查看每个变量的公因子方差数据
    column_gyzfc_res = get_result(column_gyzfc)
    titles = df.columns
    component_matrix = []  # 旋转后的成分矩阵
    for h in range(len(titles)):
        j = load_matrix_rotated.loadings_[h]
        cur_dict = {index: process_nan(value) for index, value in enumerate(j)}
        component_matrix.append({'row_key': str(titles[h]), 'values': cur_dict})

    # 计算kmo
    kmo_all, kmo_model = calculate_kmo(df)
    # 进行 Bartlett 球形度检验
    chi_square_value, p_value = calculate_bartlett_sphericity(df)
    # 计算自由度
    p = df.shape[1]
    dof = p * (p - 1) / 2

    res = {
        'kmo_bartlett':
            {
                'kmo': str(kmo_model),
                'bartlett':
                    {
                        'square': str(chi_square_value),
                        'dof': str(dof),
                        'p_value': str(p_value)
                    }
            },
        'component_matrix': component_matrix,
        'total_variance_explanation': total_matric,
        'communalities': column_gyzfc_res,
    }
    return res


class Validity(object):
    def __init__(self, df: pd.DataFrame, groups: dict, kmo_min, kmo_max):
        self.df = df
        self.groups = groups
        self.kmo_min = kmo_min
        self.kmo_max = kmo_max

    def get_groups_kmo(self) -> dict:
        kmo_dict = dict()
        try:
            cur_df = self.df
            cur_groups = self.groups
            for i in cur_groups:
                group_index = i.get('group_index')  # 拿到索引名称
                indexes = i.get('indexes')  # 拿到每一组的索引id
                dict_keys = list(indexes)
                inner_df = cur_df[dict_keys]
                kmo_all, kmo_model = calculate_kmo(inner_df)
                kmo_value = "{:.3f}".format(kmo_model) if kmo_model else ""
                kmo_dict[group_index] = kmo_value
        except Exception as e:
            traceback.print_exc()
            raise Exception
        finally:
            return kmo_dict

    def do_analysis(self):
        """
        开始分析
        """

        def process_nan(value):
            if isinstance(value, float) and value != value:
                return None  # 替换 NaN 值为 None 或其他值
            elif isinstance(value, dict):
                return {k: str(process_nan(v)) for k, v in value.items()}
            elif isinstance(value, list):
                return [str(process_nan(v)) for v in value]
            else:
                return str(value)

        def get_result(obj: pd.DataFrame) -> []:
            output = []
            for index, row in obj.iterrows():
                output.append(
                    {'row_key': str(index), 'values': {str(col): process_nan(row[col]) for col in obj.columns}})
            return output

        def result_pass(obj, factor_count):
            """
            判断结果是否通过
            :return:
            """
            cur_score = 0
            pass_res = True
            explain_min = 0.4
            explain_max = 0.6
            # 1. 判断kmo值
            total_kmo = obj.get('kmo_bartlett')
            if not total_kmo:
                return False, cur_score
            kmo_str = total_kmo.get('kmo')
            if not kmo_str:
                return False, cur_score
            if float(kmo_str) < self.kmo_min or float(kmo_str) > self.kmo_max:
                return False, cur_score
            total_variance_explanation = obj.get('total_variance_explanation')
            # 2. 判断每个因子的kmo
            item_kmos = obj.get('item_kmo')
            if item_kmos:
                kmos = list(item_kmos.values())
                for kmo in kmos:
                    if self.kmo_min < float(kmo) < self.kmo_max:
                        cur_score += 1
                    else:
                        return False, cur_score

            # 2. 判断40和60
            if not total_variance_explanation:
                return False, cur_score
            min_explanation = 100
            max_explanation = 0
            for index in range(len(total_variance_explanation)):
                vo = total_variance_explanation[index].get('values')
                if index == 0:
                    cur_min = float(vo.get('5'))  # 获取最小的
                    if cur_min < min_explanation:
                        min_explanation = cur_min
                elif index == factor_count - 1:
                    cur_max = float(vo.get('8'))  # 获取最大的
                    if cur_max > max_explanation:
                        max_explanation = cur_max
            # print('max___:', max_explanation, ',,,,,min___:', min_explanation)
            if min_explanation > explain_min or max_explanation < explain_max:
                if min_explanation > explain_min:
                    cur_score -= (min_explanation - explain_min) * 10
                if max_explanation < explain_max:
                    cur_score -= (explain_max - max_explanation) * 10
                return False, cur_score
            return pass_res, cur_score

        score = 0
        df = self.df
        load_matrix = FactorAnalyzer(rotation=None, n_factors=len(df.T), method='principal')
        load_matrix.fit(df)
        f_contribution_var = load_matrix.get_factor_variance()
        matrices_var = pd.DataFrame()  # 旋转前的载荷矩阵的贡献率
        matrices_var["0"] = f_contribution_var[0]
        matrices_var["1"] = f_contribution_var[1]
        matrices_var["2"] = f_contribution_var[2]

        eigenvalues = 1
        count = 0
        for c in matrices_var["0"]:
            if c >= eigenvalues:
                count += 1
            else:
                s = matrices_var["2"][count - 1]
                # print("\n选择了" + str(count) + "个因子累计贡献率为" + str(s) + "\n")
                break
        matrices_pass = True
        if count != len(self.groups):
            # 因子划分有问题，不通过
            matrices_pass = False
            score -= 10000  # 因子划分有问题，则数据完全不可用
        else:
            score += 10

        df_before_matric = pd.DataFrame()  # 旋转前的载荷矩阵的贡献率
        df_before_matric["3"] = f_contribution_var[0][:count]
        df_before_matric["4"] = f_contribution_var[1][:count]
        df_before_matric["5"] = f_contribution_var[2][:count]

        load_matrix_rotated = FactorAnalyzer(rotation='varimax', n_factors=count, method='principal')
        load_matrix_rotated.fit(df)
        f_contribution_var_rotated = load_matrix_rotated.get_factor_variance()
        matrices_var_rotated = pd.DataFrame()  # 旋转后的载荷矩阵的贡献率
        matrices_var_rotated["6"] = f_contribution_var_rotated[0]
        matrices_var_rotated["7"] = f_contribution_var_rotated[1]
        matrices_var_rotated["8"] = f_contribution_var_rotated[2]
        total_matric_df = matrices_var.join(df_before_matric).join(matrices_var_rotated)
        total_matric = get_result(total_matric_df)  # 总方差解释

        # gyzfc = load_matrix_rotated.get_communalities()  # 公因子方差
        column_gyzfc = pd.DataFrame(load_matrix_rotated.get_communalities(), index=df.columns)  # 查看每个变量的公因子方差数据
        column_gyzfc_res = get_result(column_gyzfc)
        titles = df.columns
        component_matrix = []  # 旋转后的成分矩阵
        for h in range(len(titles)):
            j = load_matrix_rotated.loadings_[h]
            cur_dict = {index: process_nan(value) for index, value in enumerate(j)}
            component_matrix.append({'row_key': str(titles[h]), 'values': cur_dict})

        # 计算kmo
        kmo_all, kmo_model = calculate_kmo(df)
        # 进行 Bartlett 球形度检验
        chi_square_value, p_value = calculate_bartlett_sphericity(df)
        # 计算自由度
        p = df.shape[1]
        dof = p * (p - 1) / 2

        res = {
            'kmo_bartlett':
                {
                    'kmo': str(kmo_model),
                    'bartlett':
                        {
                            'square': str(chi_square_value),
                            'dof': str(dof),
                            'p_value': str(p_value)
                        }
                },
            'item_kmo': self.get_groups_kmo(),
            'component_matrix': component_matrix,
            'total_variance_explanation': total_matric,
            'communalities': column_gyzfc_res,
        }
        if matrices_pass:
            is_pass, pass_score = result_pass(res, count)
        else:
            is_pass = matrices_pass
            pass_score = 0
        score += pass_score
        return {'res': res, 'is_pass': is_pass, 'score': score}

    def report_do_analysis(self):
        """
        输出报告做分析专用
        """

        def process_nan(value):
            if isinstance(value, float) and value != value:
                return None  # 替换 NaN 值为 None 或其他值
            elif isinstance(value, dict):
                return {k: str(process_nan(v)) for k, v in value.items()}
            elif isinstance(value, list):
                return [str(process_nan(v)) for v in value]
            else:
                return str(value)

        def get_result(obj: pd.DataFrame) -> []:
            output = []
            for index, row in obj.iterrows():
                output.append(
                    {'row_key': str(index), 'values': {str(col): process_nan(row[col]) for col in obj.columns}})
            return output

        df = self.df
        load_matrix = FactorAnalyzer(rotation=None, n_factors=len(df.T), method='principal')
        load_matrix.fit(df)
        f_contribution_var = load_matrix.get_factor_variance()
        matrices_var = pd.DataFrame()  # 旋转前的载荷矩阵的贡献率
        matrices_var["0"] = f_contribution_var[0]
        matrices_var["1"] = f_contribution_var[1]
        matrices_var["2"] = f_contribution_var[2]

        eigenvalues = 1
        count = 0
        for c in matrices_var["0"]:
            if c >= eigenvalues:
                count += 1
            else:
                # s = matrices_var["2"][count - 1]
                break

        df_before_matric = pd.DataFrame()  # 旋转前的载荷矩阵的贡献率
        df_before_matric["3"] = f_contribution_var[0][:count]
        df_before_matric["4"] = f_contribution_var[1][:count]
        df_before_matric["5"] = f_contribution_var[2][:count]

        load_matrix_rotated = FactorAnalyzer(rotation='varimax', n_factors=count, method='principal')
        load_matrix_rotated.fit(df)
        f_contribution_var_rotated = load_matrix_rotated.get_factor_variance()
        matrices_var_rotated = pd.DataFrame()  # 旋转后的载荷矩阵的贡献率
        matrices_var_rotated["6"] = f_contribution_var_rotated[0]
        matrices_var_rotated["7"] = f_contribution_var_rotated[1]
        matrices_var_rotated["8"] = f_contribution_var_rotated[2]
        total_matric_df = matrices_var.join(df_before_matric).join(matrices_var_rotated)
        total_matric = get_result(total_matric_df)  # 总方差解释

        # gyzfc = load_matrix_rotated.get_communalities()  # 公因子方差
        column_gyzfc = pd.DataFrame(load_matrix_rotated.get_communalities(), index=df.columns)  # 查看每个变量的公因子方差数据
        column_gyzfc_res = get_result(column_gyzfc)
        titles = df.columns
        component_matrix = []  # 旋转后的成分矩阵
        for h in range(len(titles)):
            j = load_matrix_rotated.loadings_[h]
            cur_dict = {index: process_nan(value) for index, value in enumerate(j)}
            component_matrix.append({'row_key': str(titles[h]), 'values': cur_dict})

        # 计算kmo
        kmo_all, kmo_model = calculate_kmo(df)
        # 进行 Bartlett 球形度检验
        chi_square_value, p_value = calculate_bartlett_sphericity(df)
        # 计算自由度
        p = df.shape[1]
        dof = p * (p - 1) / 2

        res = {
            'kmo_bartlett':
                {
                    'kmo': str(kmo_model),
                    'bartlett':
                        {
                            'square': str(chi_square_value),
                            'dof': str(dof),
                            'p_value': str(p_value)
                        }
                },
            'item_kmo': self.get_groups_kmo(),
            'component_matrix': component_matrix,
            'total_variance_explanation': total_matric,
            'communalities': column_gyzfc_res,
        }
        return res
